/**
 * 
 */
package org.mediahouse.trac.http;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 
 * @author Tin
 * @version %I%, %G%
 * 
 */
public abstract class AbstractCustomHttpServlet extends HttpServlet implements CustomHttpServlet {

    private static final long serialVersionUID = 1L;

    /**
     * Arbitrary no-arguments constructor inherited from the
     * {@linkplain HttpServlet} superclass. This constructor is not intended to
     * be invoked directly, but rather is exposed for the benefit of any servlet
     * containers, which generally require that servlets contain a single
     * no-arguments constructor (any overloaded constructors will be ignored).
     * If this constructor is used directly from this abstract class, then at
     * least the {@link #doRequest} method must be implemented.
     * <p>
     * The no-arguments constructor may be safely redefined in implementation
     * classes without requiring any call to {@code super()} (as there are no
     * hidden fields set in this constructor), although generally redefining
     * this constructor is not necessary, and using the default constructor will
     * suffice.
     * <p>
     * It is <strong>strongly recommended</strong> that the constructor
     * <strong>not</strong> be used to initialize fields that are meant to be
     * specific to each request. Depending on the design of its container, a
     * servlet instance may outlive the request that it was created for and may
     * be used to fulfill multiple requests. In this case attempting the above
     * introduces state to the environment, which will lead at the very least to
     * incorrect assertions regarding the timing of calls to the constructor
     * (and therefore inconsistent behavior in the implementation); if managed
     * poorly it could open the door to fatal errors, or state leaking across
     * requests.
     * <p>
     * In general, although Java servlets are usually deemed to be "stateless",
     * <em>do not simply assume</em> that the servlet environment will in fact
     * restrict the entire servlet life-cycle to a single request; in particular
     * be <em>extremely careful</em> about the use fields to cache information
     * for each distinct request in HttpServlets.
     * 
     * @see HttpServlet#HttpServlet()
     */
    public AbstractCustomHttpServlet() {
        super();
    }

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Object#clone()
     */
    @Override
    protected final CustomHttpServlet clone() throws CloneNotSupportedException {
        return (CustomHttpServlet) super.clone();
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.servlet.GenericServlet#destroy()
     */
    @Override
    public final void destroy() {
        super.destroy();
    }

    /**
     * This servlet's base request handler for {@code GET} requests.
     * 
     * @param request
     *            the {@link HttpServletRequest} object passed in by the
     *            incoming HTTP request
     * @param response
     *            the {@link HttpServletResponse} object passed in by the
     *            incoming HTTP request
     * @throws RuntimeException
     *             if a problem occurs while processing the request
     * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse)
     */
    @Override
    protected final void doGet(final HttpServletRequest request, final HttpServletResponse response)
            throws RuntimeException {
        forwardRequest(request, response, HttpRequestMethodType.GET);
    }

    /**
     * This servlet's base request handler for {@code POST} requests.
     * 
     * @param request
     *            the {@link HttpServletRequest} object passed in by the
     *            incoming HTTP request
     * @param response
     *            the {@link HttpServletResponse} object passed in by the
     *            incoming HTTP request
     * @throws RuntimeException
     *             if a problem occurs while processing the request
     * @see HttpServlet#doPost(HttpServletRequest, HttpServletResponse)
     */
    @Override
    protected final void doPost(final HttpServletRequest request, final HttpServletResponse response)
            throws RuntimeException {
        forwardRequest(request, response, HttpRequestMethodType.POST);
    }

    /**
     * This servlet's base request handler for {@code PUT} requests.
     * 
     * @param request
     *            the {@link HttpServletRequest} object passed in by the
     *            incoming HTTP request
     * @param response
     *            the {@link HttpServletResponse} object passed in by the
     *            incoming HTTP request
     * @throws RuntimeException
     *             if a problem occurs while processing the request
     * @see HttpServlet#doPut(HttpServletRequest, HttpServletResponse)
     */
    @Override
    protected final void doPut(final HttpServletRequest request, final HttpServletResponse response)
            throws RuntimeException {
        forwardRequest(request, response, HttpRequestMethodType.PUT);
    }

    /**
     * This servlet's base request handler for {@code DELETE} requests.
     * 
     * @param request
     *            the {@link HttpServletRequest} object passed in by the
     *            incoming HTTP request
     * @param response
     *            the {@link HttpServletResponse} object passed in by the
     *            incoming HTTP request
     * @throws RuntimeException
     *             if a problem occurs while processing the request
     * @see HttpServlet#doDelete(HttpServletRequest, HttpServletResponse)
     */
    @Override
    protected final void doDelete(final HttpServletRequest request, final HttpServletResponse response)
            throws RuntimeException {
        forwardRequest(request, response, HttpRequestMethodType.DELETE);
    }

    /**
     * This servlet's base request handler for {@code HEAD} requests.
     * 
     * @param request
     *            the {@link HttpServletRequest} object passed in by the
     *            incoming HTTP request
     * @param response
     *            the {@link HttpServletResponse} object passed in by the
     *            incoming HTTP request
     * @throws RuntimeException
     *             if a problem occurs while processing the request
     * @see HttpServlet#doHead(HttpServletRequest, HttpServletResponse)
     */
    @Override
    protected final void doHead(final HttpServletRequest request, final HttpServletResponse response)
            throws RuntimeException {
        forwardRequest(request, response, HttpRequestMethodType.HEAD);
    }

    /**
     * This servlet's base request handler for {@code OPTIONS} requests.
     * 
     * @param request
     *            the {@link HttpServletRequest} object passed in by the
     *            incoming HTTP request
     * @param response
     *            the {@link HttpServletResponse} object passed in by the
     *            incoming HTTP request
     * @throws RuntimeException
     *             if a problem occurs while processing the request
     * @see HttpServlet#doOptions(HttpServletRequest, HttpServletResponse)
     */
    @Override
    protected void doOptions(final HttpServletRequest request, final HttpServletResponse response)
            throws ServletException, IOException {
        super.doOptions(request, response);
    }

    /**
     * This servlet's base request handler for {@code TRACE} requests.
     * 
     * @param request
     *            the {@link HttpServletRequest} object passed in by the
     *            incoming HTTP request
     * @param response
     *            the {@link HttpServletResponse} object passed in by the
     *            incoming HTTP request
     * @throws RuntimeException
     *             if a problem occurs while processing the request
     * @see HttpServlet#doTrace(HttpServletRequest, HttpServletResponse)
     */
    @Override
    protected void doTrace(final HttpServletRequest request, final HttpServletResponse response)
            throws ServletException, IOException {
        super.doTrace(request, response);
    }

    /**
     * {@inheritDoc}
     * <p>
     * This method is set by default to receive all {@code GET,} {@code POST,}
     * {@code PUT,} {@code DELETE} and {@code HEAD} requests sent to this
     * CustomHttpServlet. This can be overridden for each request method by
     * redefining the corresponding HttpServlet {@code doGet,} {@code doPost,}
     * {@code doPut,} {@code doDelete} or {@code doHead} methods in the
     * overriding implementation class (provided that it is not declared final).
     */
    @Override
    public abstract void doRequest(HttpServletRequest request, HttpServletResponse response,
            HttpRequestMethodType method) throws Exception;

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public final boolean equals(final Object obj) {
        return super.equals(obj);
    }

    /**
     * Sends an error response to the client using the specified status code and
     * clears the buffer. The server defaults to creating the response to look
     * like an HTML-formatted server error page containing the specified
     * message, setting the content type to "text/html". The server will
     * preserve cookies and may clear or update any headers needed to serve the
     * error page as a valid response. If an error-page declaration has been
     * made for the web application corresponding to the status code passed in,
     * it will be served back in preference to the suggested msg parameter and
     * the msg parameter will be ignored.
     * <p>
     * If the response has already been committed, this method throws an
     * <code>IllegalStateException.</code> After using this method, the response
     * should be considered to be committed and should not be written to.
     * 
     * @param status
     *            the error status code
     * @param message
     *            the descriptive message
     * @param response
     *            the HttpServletResponse object to forward
     * @throws IOException
     *             - If an input or output exception occurs
     * @throws IllegalStateException
     *             - If the response was committed before this method call
     */
    protected final void error(final HttpStatus status, final String message,
            final HttpServletResponse response) throws IOException {
        response.sendError(status.code, message);
    }

    /**
     * Sends an error response to the client using the specified status code and
     * clears the buffer. The server will preserve cookies and may clear or
     * update any headers needed to serve the error page as a valid response. If
     * an error-page declaration has been made for the web application
     * corresponding to the status code passed in, it will be served back.
     * <p>
     * If the response has already been committed, this method throws an
     * <code>IllegalStateException.</code> After using this method, the response
     * should be considered to be committed and should not be written to.
     * 
     * @param status
     *            the error status code
     * @param response
     *            the HttpServletResponse object to forward
     * @throws IOException
     *             - If an input or output exception occurs
     * @throws IllegalStateException
     *             - If the response was committed before this method call
     */
    protected final void error(final HttpStatus status,
            final HttpServletResponse response) throws IOException {
        this.error(status, "There was an error processing your request.", response);
    }

    /**
     * @throws UnsupportedOperationException
     *             not implemented yet.
     */
    protected final void error(final HttpStatus status, final String message) {
        throw new UnsupportedOperationException("Not implemented yet.");
    }

    /**
     * @throws UnsupportedOperationException
     *             not implemented yet.
     */
    protected final void error(final HttpStatus status) {
        throw new UnsupportedOperationException("Not implemented yet.");
    }

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Object#finalize()
     */
    @Override
    protected final void finalize() throws Throwable {
        super.finalize();
    }

    private final void forwardRequest(final HttpServletRequest request,
            final HttpServletResponse response, final HttpRequestMethodType method) {
        try {
            doRequest(request, response, method);
        } catch (final Throwable e) {
            handleRequestError(e);
        }
    }

    /**
     * Defines the way that this custom HttpServlet implementation will handle
     * any errors that are thrown up by its {@linkplain #doRequest} handler
     * while trying to handle a request.
     * <p>
     * <strong>Important: if this method is not overridden in the implementation
     * class, then the default response will be to escalate the
     * {@link Throwable} cause to the application container wrapped as a
     * RuntimeException.</strong> Be aware that this will <em>very likely</em>
     * cause the application environment to enter an undesirable invalid state,
     * and possibly cause it to crash and stop responding.
     * 
     * @param cause
     *            the error or uncaught exception that was thrown by this
     *            {@code HttpServlet} implementation's request handler
     */
    public void handleRequestError(final Throwable cause) {
        throw new RuntimeException("An error was thrown while processing the request.", cause);
    }

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Object#hashCode()
     */
    @Override
    public final int hashCode() {
        return super.hashCode();
    }

    /**
     * @throws UnsupportedOperationException
     *             not implemented yet.
     */
    protected final HttpServletResponse resp() {
        throw new UnsupportedOperationException("Not implemented yet");
    }

    /**
     * @throws UnsupportedOperationException
     *             not implemented yet.
     */
    protected final HttpServletRequest req() {
        throw new UnsupportedOperationException("Not implemented yet");
    }

    /**
     * Sends a temporary redirect response to the client using the specified
     * redirect location URL and clears the buffer. The buffer will be replaced
     * with the data set by this method. Calling this method sets the status
     * code to {@link HttpServletResponse.SC_FOUND SC_FOUND} (302 Found). This
     * method can accept relative URLs; the container must convert the relative
     * URL to an absolute URL before sending the response to the client. If the
     * location is relative without a leading {@code '/'} the container
     * interprets it as relative to the current request URI. If the location is
     * relative with a leading {@code '/'} the container interprets it as
     * relative to the container root.
     * <p>
     * This method throws an {@code IllegalStateException} if the response has
     * already been committed. After using this method, the response should be
     * considered to be committed and should not be written to.
     * 
     * @param location
     *            the URL pattern associated with another HttpServlet or JSP
     *            file to redirect this request to
     * @param response
     *            the HttpServletResponse object to forward
     * @throws IllegalArgumentException
     *             if the given URL string is not a valid URL
     * @throws IllegalStateException
     *             if the HttpServletResponse has already been committed or if a
     *             partial URL is given and cannot be converted into a valid URL
     * @throws IOException
     *             if the redirect attempt fails due to the occurrence of an
     *             input or output exception, if the destination HttpServlet or
     *             JSP file cannot be found
     */
    protected final void redirect(final String location, final HttpServletResponse response) throws IllegalArgumentException, IOException {
        if (!UrlValidator.tryParse(location)) throw new IllegalArgumentException(
                "Bad URL; the URL cannot be " + String.valueOf(location));
        response.setStatus(HttpServletResponse.SC_FOUND);
        status(HttpStatus.FOUND, response);
        response.sendRedirect(location);
    }

    protected final void status(final HttpStatus status, final HttpServletResponse response) {
        response.setStatus(status.code);
    }

    /**
     * @throws UnsupportedOperationException
     *             not implemented yet.
     */
    protected final void redirect(final String location) {
        throw new UnsupportedOperationException("Not implemented yet.");
    }

    /**
     * @throws UnsupportedOperationException
     *             not implemented yet.
     */
    protected final void status(final HttpStatus status) {
        throw new UnsupportedOperationException("Not implemented yet.");
    }

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public final String toString() {
        return super.toString();
    }

    /**
     * @author Tin
     * @version %I%, %G%
     *
     */
    public enum HttpStatus {
        /** */
        ACCEPTED(HttpServletResponse.SC_ACCEPTED),
        /** */
        BAD_GATEWAY(HttpServletResponse.SC_BAD_GATEWAY),
        /** */
        BAD_REQUEST(HttpServletResponse.SC_BAD_REQUEST),
        /** */
        CONFLICT(HttpServletResponse.SC_CONFLICT),
        /** */
        CONTINUE(HttpServletResponse.SC_CONTINUE),
        /** */
        CREATED(HttpServletResponse.SC_CREATED),
        /** */
        EXPECTATION_FAILED(HttpServletResponse.SC_EXPECTATION_FAILED),
        /** */
        FORBIDDEN(HttpServletResponse.SC_FORBIDDEN),
        /** */
        FOUND(HttpServletResponse.SC_FOUND),
        /** */
        GATEWAY_TIMEOUT(HttpServletResponse.SC_GATEWAY_TIMEOUT),
        /** */
        GONE(HttpServletResponse.SC_GONE),
        /** */
        HTTP_VERSION_NOT_SUPPORTED(HttpServletResponse.SC_HTTP_VERSION_NOT_SUPPORTED),
        /** */
        INTERNAL_SERVER_ERROR(HttpServletResponse.SC_INTERNAL_SERVER_ERROR),
        /** */
        LENGTH_REQUIRED(HttpServletResponse.SC_LENGTH_REQUIRED),
        /** */
        METHOD_NOT_ALLOWED(HttpServletResponse.SC_METHOD_NOT_ALLOWED),
        /** */
        MOVED_PERMANENTLY(HttpServletResponse.SC_MOVED_PERMANENTLY),
        /** */
        MOVED_TEMPORARILY(HttpServletResponse.SC_MOVED_TEMPORARILY),
        /** */
        MULTIPLE_CHOICES(HttpServletResponse.SC_MULTIPLE_CHOICES),
        /** */
        NO_CONTENT(HttpServletResponse.SC_NO_CONTENT),
        /** */
        NON_AUTHORITATIVE_INFORMATION(HttpServletResponse.SC_NON_AUTHORITATIVE_INFORMATION),
        /** */
        NOT_ACCEPTABLE(HttpServletResponse.SC_NOT_ACCEPTABLE),
        /** */
        NOT_FOUND(HttpServletResponse.SC_NOT_FOUND),
        /** */
        NOT_IMPLEMENTED(HttpServletResponse.SC_NOT_IMPLEMENTED),
        /** */
        NOT_MODIFIED(HttpServletResponse.SC_NOT_MODIFIED),
        /** */
        OK(HttpServletResponse.SC_OK),
        /** */
        PARTIAL_CONTENT(HttpServletResponse.SC_PARTIAL_CONTENT),
        /** */
        PAYMENT_REQUIRED(HttpServletResponse.SC_PAYMENT_REQUIRED),
        /** */
        PRECONDITION_FAILED(HttpServletResponse.SC_PRECONDITION_FAILED),
        /** */
        PROXY_AUTHENTICATION_REQUIRED(HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED),
        /** */
        REQUEST_ENTITY_TOO_LARGE(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE),
        /** */
        REQUEST_TIMEOUT(HttpServletResponse.SC_REQUEST_TIMEOUT),
        /** */
        REQUEST_URI_TOO_LONG(HttpServletResponse.SC_REQUEST_URI_TOO_LONG),
        /** */
        REQUESTED_RANGE_NOT_SATISFIABLE(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE),
        /** */
        RESET_CONTENT(HttpServletResponse.SC_RESET_CONTENT),
        /** */
        SEE_OTHER(HttpServletResponse.SC_SEE_OTHER),
        /** */
        SERVICE_UNAVAILABLE(HttpServletResponse.SC_SERVICE_UNAVAILABLE),
        /** */
        SWITCHING_PROTOCOLS(HttpServletResponse.SC_SWITCHING_PROTOCOLS),
        /** */
        TEMPORARY_REDIRECT(HttpServletResponse.SC_TEMPORARY_REDIRECT),
        /** */
        UNAUTHORIZED(HttpServletResponse.SC_UNAUTHORIZED),
        /** */
        UNSUPPORTED_MEDIA_TYPE(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE),
        /** */
        USE_PROXY(HttpServletResponse.SC_USE_PROXY);

        private final int code;

        private HttpStatus(final int code) {
            this.code = code;
        }

        /**
         * @return the status code as an {@code integer} value as per the
         *         specification.
         */
        public int getCode() {
            return code;
        }
    }

    private static class UrlValidator {

        public static boolean tryParse(final String url) {
            return url != null;
        }
    }
}